记录 9x07平台的网口相关功能的研究过程,包含QMI、smd、网口等功能

rmnet USB网口

初始化

  1. 创建5控制接口misc设备,供应用层使用

    function初始化rmnet_function_init —> rmnet_init —> gqti_ctrl_init

    rmnet_ctrl、rmnet_ctrl1、rmnet_ctrl2、rmnet_ctr3、dpl_ctrl

  2. 初始化USB端点

    应用层:echo QTI,BAM_DMUX > f_rmnet/transports

    frmnet_init_port,初始化网络接口设备和端口:f_rmnet、rmnet_port

    • 控制接口类型:QTI
    • 数据接口类型:BAM_DMUX

    frmnet_bind,初始化function interface

    创建3个endpoint

    • 数据TX,从机 —> 主机(IN endpoint)

    • 数据RX,主机 —> 从机(OUT endpoint)

    • 事件通知,从机 —> 主机(IN endpoint)

      初始接口和端点的描述

  3. 通讯方式

    应用层打开/dev/rmnet_ctrl和/dev/dpl_ctrl

    应用层通过/dev/rmnet_ctrl和/dev/dpl_ctrl与host端程序通讯

设置接口

usb连接时,host端驱动执行USB接口设置,触发frmnet_set_alt

  1. 初始化&打开事件通知端点
  2. 执行gport_rmnet_connect,连接初始化
    • 控制通道连接初始化
    • 数据通道连接初始化

控制通道连接初始化

gsmd_ctrl_connect

初始化2个回调函数

1
2
g_rmnet->send_encap_cmd = gqti_ctrl_send_cpkt_tomodem;
g_rmnet->notify_modem = gqti_ctrl_notify_modem;

执行g_rmnet->connect(port->port_usb) —> frmnet_connect

数据通道连接初始化

gbam_connect

初始化&打开数据TX、数据RX端点

触发gbam_connect_work

1
2
INIT_WORK(&port->connect_w, gbam_connect_work);	
queue_work(gbam_wq, &port->connect_w);

gbam_connect_work

打开bam

1
ret = msm_bam_dmux_open(d->id, port, gbam_notify);

为TX和RX端口申请usb_request

gbam_start_io —> _gbam_start_io —> gbam_alloc_requests

TX和RX端点的申请大小及完成回调

1
2
3
4
5
6
7
8
9
10
11
12
13
if (in) {
ep = port->port_usb->in;
idle = &port->data_ch.tx_idle;
queue_size = bam_mux_tx_q_size;
ep_complete = gbam_epin_complete;
} else {
ep = port->port_usb->out;
if (!ep)
goto out;
idle = &port->data_ch.rx_idle;
queue_size = bam_mux_rx_q_size;
ep_complete = gbam_epout_complete;
}

通讯

host向device发命令

host驱动 —> usb —> device应用

使用usb的setup,由frmnet_setup处理理

命令:USB_CDC_SEND_ENCAPSULATED_COMMAND

1
2
3
4
5
6
7
8
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| USB_CDC_SEND_ENCAPSULATED_COMMAND:
pr_debug("%s: USB_CDC_SEND_ENCAPSULATED_COMMAND\n"
, __func__);
ret = w_length;
req->complete = frmnet_cmd_complete;
req->context = dev;
break;

frmnet_cmd_complete —> dev->port.send_encap_cmd —> gqti_ctrl_send_cpkt_tomodem

其中参数port使用ctrl_xport_num

1
2
3
4
5
6
7
8
case USB_GADGET_XPORT_QTI:
rmnet_port->ctrl_xport_num = no_ctrl_qti_ports;
//此时ctrl_xport_num==0

if (dev->port.send_encap_cmd) {
port_num = rmnet_ports[dev->port_num].ctrl_xport_num;
dev->port.send_encap_cmd(port_num, req->buf, req->actual);
}

gqti_ctrl_send_cpkt_tomodem

将数据放到队列中,唤醒读取的任务

1
2
3
4
5
6
7
8
port = ctrl_port[portno]; //端口号为0,使用/dev/rmnet_ctrl
cpkt = alloc_rmnet_ctrl_pkt(len, GFP_ATOMIC);

memcpy(cpkt->buf, buf, len);
cpkt->len = len;

list_add_tail(&cpkt->list, &port->cpkt_req_q);
wake_up(&port->read_wq);

device应用读取

qti_ctrl_read

1
2
3
cpkt = list_first_entry(&port->cpkt_req_q, struct rmnet_ctrl_pkt,
list);
ret = copy_to_user(buf, cpkt->buf, cpkt->len);

device向host发命令

device应用 —> usb —> host驱动

qti_ctrl_write

1
2
3
ret = copy_from_user(kbuf, buf, count);
ret = g_rmnet->send_cpkt_response(port->port_usb,
kbuf, count);
1
dev->port.send_cpkt_response = frmnet_send_cpkt_response;

frmnet_send_cpkt_response

1
2
3
4
5
6
cpkt = rmnet_alloc_ctrl_pkt(len, GFP_ATOMIC);
memcpy(cpkt->buf, buf, len);
cpkt->len = len;

list_add_tail(&cpkt->list, &dev->cpkt_resp_q);//先将数据存起来
frmnet_ctrl_response_available(dev);//然后通过notify端点向host发消息

host收到消息后再通过setup来取数据

1
2
3
4
5
6
7
8
9
10
11
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
| USB_CDC_GET_ENCAPSULATED_RESPONSE:
pr_debug("%s: USB_CDC_GET_ENCAPSULATED_RESPONSE\n", __func__);

cpkt = list_first_entry(&dev->cpkt_resp_q,
struct rmnet_ctrl_pkt, list);
list_del(&cpkt->list);
spin_unlock(&dev->lock);

len = min_t(unsigned, w_length, cpkt->len);
memcpy(req->buf, cpkt->buf, len);

host向device发数据

host驱动 —> usb —> bam —> modem

host通过数据RX端点(OUT)发数据

device收到数据,执行gbam_epout_complete

1
2
3
4
skb_put(skb, req->actual);
__skb_queue_tail(&d->rx_skb_q, skb);

queue_work(gbam_wq, &d->write_tobam_w);

数据存放到链表,通知处理任务

1
INIT_WORK(&d->write_tobam_w, gbam_data_write_tobam);

gbam_data_write_tobam —> msm_bam_dmux_write

device向host发数据

modem —> bam —> usb —> host驱动

  1. 数据通道连接初始化时open bam时设置了回调gbam_notify
  2. gbam_notify处理消息BAM_DMUX_RECEIVE
1
2
3
4
5
6
7
8
switch (event) {
case BAM_DMUX_RECEIVE:
skb = (struct sk_buff *)data;
if (port)
gbam_data_recv_cb(p, skb);
else
dev_kfree_skb_any(skb);
break;

gbam_data_recv_cb —> gbam_write_data_tohost

BAM数据收发

bam支持的BAM通道非常多,USB用了其中2个

1
2
3
4
static unsigned bam_ch_ids[BAM_N_PORTS] = {
BAM_DMUX_USB_RMNET_0,
BAM_DMUX_USB_DPL
};

初始化时申请了2个bam端口供usb使用

1
2
3
4
5
6
7
static int gbam_port_alloc(int portno)
{
port = kzalloc(sizeof(struct gbam_port), GFP_KERNEL);
d->id = bam_ch_ids[portno];//主要关注BAM_DMUX_USB_RMNET_0

bam_ports[portno].port = port;
}

发送数据

gbam_data_write_tobam —> msm_bam_dmux_write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int msm_bam_dmux_write(uint32_t id, struct sk_buff *skb)
{
//填充数据包头
hdr = (struct bam_mux_hdr *)skb_push(skb, sizeof(struct bam_mux_hdr));
/* caller should allocate for hdr and padding
hdr is fine, padding is tricky */
hdr->magic_num = BAM_MUX_HDR_MAGIC_NO;
hdr->cmd = BAM_MUX_HDR_CMD_DATA;
hdr->signal = 0;
hdr->ch_id = id;
hdr->pkt_len = skb->len - sizeof(struct bam_mux_hdr);
if (skb->len & 0x3)
skb_put(skb, 4 - (skb->len & 0x3));
//使用DMA发送
pkt->skb = skb;
pkt->dma_address = dma_address;
pkt->is_cmd = 0;
}

接收数据

bamr接收数据入口:__queue_rx —> handle_bam_mux_cmd —> bam_mux_process_data

接收的数据包中通道ID,根据通道找到notify,msm_bam_dmux_open打开通道时传入

modem 网口

网口创建

高通原始代码中网口名字不叫modem,面是rmnet_data

kernel初始化时默认没有创建rmnet网口

  1. rmnet创建入口:bam_rmnet_probe
  • kernel初始化时创建了bam_dmux_ch_xx(xx表示有很多)平台驱动

  • 启动后收到了bam数据(命令:BAM_MUX_HDR_CMD_OPEN),添加平台设备bam_dmux_ch_0

    handle_bam_mux_cmd —> handle_bam_mux_cmd_open —> platform_device_add

  • 触发bam_rmnet_probe

    创建rmnet0网口,用于控制交互

  1. rmnet_data创建入口: rmnet_vnd_create_dev
  • rmnet_config_netlink_msg_handler –> rmnet_create_vnd –> rmnet_vnd_create_dev
  • 创建8个网口,rmnet_data0 - rmnet_data7,用于数据收发
  1. netmgrd初始化,将rmnet和和rmnet_data绑定

    使用 RMNET_NETLINK_SET_LOGICAL_EP_CONFIG命令

    _rmnet_netlink_set_logical_ep_config –> rmnet_set_logical_endpoint_config

    将rmnet_data0的epconfig.egress_dev指向rmnet0

打开网口

数据收发需要使用bam,因此需要打开bam

  1. netmgrd进程使用ioctl打开了rmnet0网口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    netmgr_kif_ifioctl_open_port (const char * dev)
    {
    /* Open a datagram socket to use for issuing the ioctl */
    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    //此处使用的接口名为rmnet0
    /* Set device name in ioctl req struct */
    (void)strlcpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
    /* Get current if flags for the device */
    if (ioctl(fd, RMNET_IOCTL_OPEN, &ifr) < 0) {
    }
  2. kernel中执行rmnet0网口的open操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
    {
    case RMNET_IOCTL_OPEN: /* Open transport port */
    rc = __rmnet_open(dev);

    static int __rmnet_open(struct net_device *dev)
    {
    int r;
    struct rmnet_private *p = netdev_priv(dev);

    if (p->device_up == DEVICE_UNINITIALIZED) {
    r = msm_bam_dmux_open(p->ch_id, dev, bam_notify);
    }
    p->device_up = DEVICE_ACTIVE;

    bam只打开一次,关闭接口时也不关闭bam

发送数据

数据收发使用rmnet_data0接口(现已改名成modem)

rmnet_data0接口的发送函数:rmnet_vnd_start_xmit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct rmnet_vnd_private_s *dev_conf;
trace_rmnet_vnd_start_xmit(skb);
dev_conf = (struct rmnet_vnd_private_s *) netdev_priv(dev);
if (dev_conf->local_ep.egress_dev) {
/* QoS header should come after MAP header */
if (dev_conf->qos_version)
rmnet_vnd_add_qos_header(skb,
dev,
dev_conf->qos_version);
rmnet_egress_handler(skb, &dev_conf->local_ep);
} else {
dev->stats.tx_dropped++;
rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_VND_NO_EGRESS);
}
return NETDEV_TX_OK;
}

数据由rmnet_egress_handler处理,其中dev_conf->local_ep.egress_dev在前面有初始化

1
2
3
4
5
6
7
8
9
void rmnet_egress_handler(struct sk_buff *skb,
struct rmnet_logical_ep_conf_s *ep)
{
struct rmnet_phys_ep_conf_s *config;
struct net_device *orig_dev;
int rc;
orig_dev = skb->dev;
skb->dev = ep->egress_dev; //替换skb的设备,此时egress_dev为rmnet0
rc = dev_queue_xmit(skb); //将skb存入rmnet0的队列中

触发rmnet0的发送队列:rmnet_xmit —> _rmnet_xmit —> msm_bam_dmux_write

接收数据

打开bam时传入了notify函数bam_notify

1
2
3
4
5
6
7
8
9
10
11
12
static void bam_notify(void *dev, int event, unsigned long data)
{
switch (event) {
case BAM_DMUX_RECEIVE:
bam_recv_notify(dev, (struct sk_buff *)(data));


static void bam_recv_notify(void *dev, struct sk_buff *skb)
{
if (skb) {
skb->dev = dev;
netif_rx_ni(skb); //数据包入协议栈

rmnet处理数据包:__rmnet_deliver_skb

数据包从rmnet0进入协议栈,rmnet_data0会向协议栈注册一些回调用于数据包处理,比如更新rmnet_data0的统计值:rmnet_vnd_rx_fixup,

qti数据处理

应用层启动了一个qti进程,负责与host端的驱动交互,同时通过/dev/smdcntl8与modem交互

qti接收host端数据

host端发的数据通过/dev/rmnet_ctrl到达qti进程

数据由qti_rmnet_ph_recv_msg处理,再调用qti_rmnet_modem_send_msg处理

  1. 数据交给qti_rmnet_process_qmi_tx_to_modem处理一遍(可能会修改数据)
  2. 数据转发给/dev/smdcntl8(发给modem)

注:首次接收到数据,需要打开&初始化与modem侧的通信通道,否则smdcntl8不能通信

qti接收modem数据

modem发的数据通过/dev/smdcntl8到达qti进程

数据由qti_rmnet_modem_recv_msg处理

  1. 数据交给qti_rmnet_process_qmi_rx_from_modem处理一遍

    qti修改某些服务的数据后再转给host端

  2. 数据转发给/dev/rmnet_ctrl(发给host)

打开通信通道

qti_rmnet_ph_recv_msg —> qti_rmnet_process_ph_reset

  1. 首先确认USB是否确实已经连接

    1
    2
    ret = ioctl(rmnet_state_config->ph_iface[PH_DRIVER_TYPE_USB].ph_iface_fd,
    FRMNET_CTRL_GET_LINE_STATE, &line_state);
  2. 已连接且通道没有打开,则执行打开&初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if(line_state == 1)
    {
    if(!rmnet_state_config->dtr_enabled)
    {
    //向dpm服务发请求,打开DATA40_CNTL通道
    ret_val = qti_rmnet_dpm_port_open();
    //打开smdcntl8接口,绑定qti_rmnet_modem_recv_msg处理modem的数据
    if(qti_rmnet_modem_init(rmnet_state_config,
    dpl_state_config) < 0)

    rmnet_state_config->dtr_enabled = 1;

    打开DATA40_CNTL通道后会触发Kernel里创建SDM设备(SDM里有描述)

  3. dpm服务的初始化

    进程启动时调用了qti_dpm_init,初始化了DMP qmi客户端

    QMI的初始化在QMI客户端

qmuxd进程

qmuxd进程提供qmi服务

  1. qmuxd创建/tmp/qmux_connect_socket供客户端使用
    linux_qmi_qmux_if_get_listener_socket

  2. linux_qmi_qmux_if_configure_ports,默认关闭了所以端口

    1
    2
    3
    4
    5
    6
    /* Disabling all channels initially to enable only required channels later.*/
    for (i = QMI_CONN_ID_RMNET_0; i < LINUX_QMI_MAX_CONN_SUPPORTED; i++)
    {
    qmi_qmux_disable_port(linux_qmi_conn_id_enablement_array[i].qmi_conn_id,
    linux_qmi_conn_id_enablement_array[i].data_ctl_port, TRUE);
    }
  3. 客户通过qmux_connect_socket连接qmuxd

    • 为客户端分配一个id:qmux_client_id
    • 将qmux_client_id发给客户端,后续客户端需要用这个id来通讯
    • qmuxd为客户端创建数据结构,更新linux_qmi_qmux_if_client_id_array
  4. 客户端发数据

    • client进程 —> qmux_connect_socket —> qmuxd进程
    • qmuxd进程使用qmux_client_id(连接时分配的)发数据,处理函数:qmi_qmux_tx_msg
    • client发数据的消息头里包含qmi_conn_id,既数据发给谁,默认rmnet0
      • qmi_qmux_if_internal_use_conn_id
    • 客户商使用qmi_qmux_if_send_to_qmux函数发数据
      • 最终调用 使用linux_qmi_qmux_if_client_tx_msg
      • 数据面使用qmi_qmux_if_send_qmi_msg(msg_id固定为QMI_QMUX_IF_QMI_MSG_ID)
      • 控制面使用qmi_qmux_if_send_if_msg_to_qmux(可指定msg_id)
  5. qmuxd接收客户端的消息

    • main中从客户端接收数据:linux_qmi_qmux_if_server_process_client_msg
    • 使用qmi_qmux_tx_msg转发到消息
  6. qmuxd处理客户端数据:qmi_qmux_tx_msg

    • 第一次需要连接:qmi_qmux_open_connection —> linux_qmi_qmux_io_open_conn

      因为默认所有的端口都被关闭,因此默认连接不成功

      默认客户端使用rmnet0

    • 按照客户使用的msg_id做不同分类处理

      • 客户数据面使用QMI_QMUX_IF_QMI_MSG_ID,数据转发到modem
        • 由qmi_qmux_tx_to_modem处理
        • 默认使用rmnet0,对应的控制节点为/dev/smdctl0
        • 因为端口默认是关闭的,正常数据不会被发送

qmi客户端

初始化

libqmiserver.so中有函数void __attribute__ ((constructor)) qmi_fw_cci_init(void),在main函数之前就运行,添加了一些port到xport_tbl,比如:

1
2
3
4
5
qmi_cci_xport_start(&qcci_ipc_router_ops, NULL);//最先添加,优先使用
qmi_cci_xport_start(&qmuxd_ops, (void *)QMI_CLIENT_QMUX_RMNET_INSTANCE_0);
qmi_cci_xport_start(&qmuxd_ops, (void *)QMI_CLIENT_QMUX_RMNET_USB_INSTANCE_0);
qmi_cci_xport_start(&qmuxd_ops, (void *)QMI_CLIENT_QMUX_RMNET_SMUX_INSTANCE_0);
qmi_cci_xport_start(&qmuxd_ops, (void *)QMI_CLIENT_QMUX_RMNET_MHI_INSTANCE_0);

qmi_client_init_instance —> qmi_client_get_service_instance —> qmi_client_get_service_list查的服务

  • 遍历xport_tbl表,使用lookup函数(xport_lookup)查找服务
  • qmi_client_init初始化客户端

xport_lookup

优先使用qcci_ipc_router_ops的xport_lookup

  • 通过AF_MSM_IPC连接到内核
  • 使用ioctl(命令:IPC_ROUTER_IOCTL_LOOKUP_SERVER)从内核查找服务
  • 查找使用service(服务ID)和instance(服务版本)
    • 内核提供的服务查看文件:/sys/kernel/debug/msm_ipc_route/dump_servers

其次使用qmuxd_ops的xport_lookup

  • 查找之前需要边连接qmuxd

    • 如果连接不上(qmuxd进程没有启动)则等待1分钟,尝试60次

    • qmuxd里关闭了接口,实际不生效,获取不到服务

      :虽然实际没使用qmuxd,但qmuxd进程不启动时会导致客户端每次请求等待1分钟

qmi_client_init

  1. 找出服务提供者,默认服务由qcci_ipc_router_ops提供

  2. 初始化客户端内部使用的clnt,细节在qmi_cci_client_alloc里

  3. 打开服务,ops->open —> xport_open

  4. 保存open的返回句柄,给后续使用

xport_open

获取服务时得到一个地址addr,后续数据通讯使用这个地址

服务由qcci_ipc_router_ops,所有的操作路由到kernel,由kernel处理

  1. 初始化控制消息read线程:ctrl_msg_reader_thread

    • 进程只有一个控消息线程

    • 打开socket,得到到fd,给线程用

    • 发命令IPC_ROUTER_IOCTL_BIND_CONTROL_PORT到kernel
    • 创建线程
  2. 初始化数据消息read线程:data_msg_reader_thread

    • 进程可以有多个数据息线程
    • 打开socket,得到到fd,给线程用
    • 创建线程

发送数据

qmi_client_send_msg_sync

  • 根据handle找出clnt(在qmi_cci_client_alloc中创建)
  • 从clnt中找到xport(在qmi_client_init中添加,在qmi_client_get_service_instance中初始化)
  • 数据编码&发送:encode_and_send
  • 使用qmi_cci_send发送数据
  • 使用服务提供的send发送数据:xport_send
  • 数据发给kernel(携带了服务地址)
  • 等待数据回应:qmi_cci_response_wait_loop
    • 数据消息read线程会通知当前线程

读取数据

data_msg_reader_thread

  • 从kernel读取数据,转发到应用程序
  • 从kernel读取控制消息,主要用于服务通知
    • 如果服务启动晚于应用初始化,qmi_client_init_instance 会等待服务

ipc router

从名字上看是进程间通讯路由功能,实际是实现了一个sock通讯

server <----> ipc sock core <----> client

初始化

sock_register(&msm_ipc_family_ops)

注册IPC sock类型,名字为MSM_IPC

添加诊断服务

诊断服务不是重点,不关注细节

kernel启动时注册了许多诊断服务:diag_socket_init —> __diag_socket_init

服务地址:

1
2
info->svc_id = DIAG_SVC_ID;
info->ins_id = ins_base + ins_offset;

诊断服务的创建:socket_open_server

1
2
3
4
5
6
7
8
9
10
11
12
13
info->hdl->sk->sk_data_ready = socket_data_ready;
info->hdl->sk->sk_write_space = socket_flow_cntl;

srv_addr.family = AF_MSM_IPC;
srv_addr.address.addrtype = MSM_IPC_ADDR_NAME;
srv_addr.address.addr.port_name.service = info->svc_id;
srv_addr.address.addr.port_name.instance = info->ins_id;

ret = kernel_bind(info->hdl, (struct sockaddr *)&srv_addr,
sizeof(srv_addr));
---------->
msm_ipc_router_bind
msm_ipc_router_register_server

QMI服务注册

modem 发送请求 —-> linux创建服务

modem给linux发命令:IPC_ROUTER_CTRL_CMD_NEW_SERVER,linux侧添加服务

1
2
case IPC_ROUTER_CTRL_CMD_NEW_SERVER:
rc = process_new_server_msg(xprt_info, msg, pkt);

注:新建服务时使用xprt_info,后续客户端收发数据使用xprt_info->xprt提供的接口

process_new_server_msg

使用服务的ID(node_id, port_id)创建entry和port供后续查找数据结构使用

1
2
3
4
5
6
7
rt_entry = ipc_router_get_rtentry_ref(msg->srv.node_id);
if (!rt_entry) {
rt_entry = create_routing_table_entry(msg->srv.node_id,
xprt_info);
}
rport_ptr = ipc_router_create_rport(msg->srv.node_id,
msg->srv.port_id, xprt_info);

使用服务地址(service, instance)创建服务

1
2
3
server = msm_ipc_router_create_server(
msg->srv.service, msg->srv.instance,
msg->srv.node_id, msg->srv.port_id, xprt_info);

xprt_info和modem数据如何达到linux侧的细节在后面讲

ipc route smd

实现linux侧数据与共享内存设备(包含modem)数据交互,以modem举例

server(modem) <----> ipc sock core <----> client(应用app)

初始化

msm_ipc_router_smd_xprt_init,添加平台驱动:ipc_router_smd_xprt

设备树配置

qcom,ipc_router_modem_xprt {
    compatible = "qcom,ipc_router_smd_xprt";
    qcom,ch-name = "IPCRTR";
    qcom,xprt-remote = "modem";
    qcom,xprt-linkid = <1>;
    qcom,xprt-version = <1>;
    qcom,fragmented-data;
    qcom,disable-pil-loading;
};

probe时初始化服务: msm_ipc_router_smd_xprt_probe —> msm_ipc_router_smd_config_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static int msm_ipc_router_smd_config_init(
struct msm_ipc_router_smd_xprt_config *smd_xprt_config)
{
struct msm_ipc_router_smd_xprt *smd_xprtp;

smd_xprtp = kzalloc(sizeof(struct msm_ipc_router_smd_xprt), GFP_KERNEL);
if (IS_ERR_OR_NULL(smd_xprtp)) {
IPC_RTR_ERR("%s: kzalloc() failed for smd_xprtp id:%s\n",
__func__, smd_xprt_config->ch_name);
return -ENOMEM;
}

smd_xprtp->xprt.link_id = smd_xprt_config->link_id;
smd_xprtp->xprt_version = smd_xprt_config->xprt_version;
smd_xprtp->edge = smd_xprt_config->edge;
smd_xprtp->xprt_option = smd_xprt_config->xprt_option;
smd_xprtp->disable_pil_loading = smd_xprt_config->disable_pil_loading;

strlcpy(smd_xprtp->ch_name, smd_xprt_config->ch_name,
SMD_MAX_CH_NAME_LEN);

strlcpy(smd_xprtp->xprt_name, smd_xprt_config->xprt_name,
XPRT_NAME_LEN);
smd_xprtp->xprt.name = smd_xprtp->xprt_name;

smd_xprtp->xprt.set_version =
ipc_router_smd_set_xprt_version;
smd_xprtp->xprt.get_version =
msm_ipc_router_smd_get_xprt_version;
smd_xprtp->xprt.get_option =
msm_ipc_router_smd_get_xprt_option;
smd_xprtp->xprt.read_avail = NULL;
smd_xprtp->xprt.read = NULL;
smd_xprtp->xprt.write_avail =
msm_ipc_router_smd_remote_write_avail;
smd_xprtp->xprt.write = msm_ipc_router_smd_remote_write;//客户端的数据由write发给smd
smd_xprtp->xprt.close = msm_ipc_router_smd_remote_close;
smd_xprtp->xprt.sft_close_done = smd_xprt_sft_close_done;
smd_xprtp->xprt.priv = NULL;

init_waitqueue_head(&smd_xprtp->write_avail_wait_q);
smd_xprtp->in_pkt = NULL;
smd_xprtp->is_partial_in_pkt = 0;
INIT_DELAYED_WORK(&smd_xprtp->read_work, smd_xprt_read_data);//从远端读取数据
spin_lock_init(&smd_xprtp->ss_reset_lock);
smd_xprtp->ss_reset = 0;

msm_ipc_router_smd_driver_register(smd_xprtp);//注册驱动

return 0;
}

使用设备树里的名字IPCRTR注册平台驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static int msm_ipc_router_smd_driver_register(
struct msm_ipc_router_smd_xprt *smd_xprtp)
{
int ret;
struct msm_ipc_router_smd_xprt *item;
unsigned already_registered = 0;

mutex_lock(&smd_remote_xprt_list_lock_lha1);
list_for_each_entry(item, &smd_remote_xprt_list, list) {
if (!strcmp(smd_xprtp->ch_name, item->ch_name))
already_registered = 1;
}
list_add(&smd_xprtp->list, &smd_remote_xprt_list);
mutex_unlock(&smd_remote_xprt_list_lock_lha1);

if (!already_registered) {
smd_xprtp->driver.driver.name = smd_xprtp->ch_name;
smd_xprtp->driver.driver.owner = THIS_MODULE;
smd_xprtp->driver.probe = msm_ipc_router_smd_remote_probe;

ret = platform_driver_register(&smd_xprtp->driver);
if (ret) {
IPC_RTR_ERR(
"%s: Failed to register platform driver [%s]\n",
__func__, smd_xprtp->ch_name);
return ret;
}
} else {
IPC_RTR_ERR("%s Already driver registered %s\n",
__func__, smd_xprtp->ch_name);
}
return 0;
}

如果创建IPCRTR平台设备,触发msm_ipc_router_smd_remote_probe

1
2
3
4
5
6
7
8
9
10
11
12
static int msm_ipc_router_smd_remote_probe(struct platform_device *pdev)
{
smd_xprtp = find_smd_xprt_list(pdev);
smd_xprtp->pil = msm_ipc_load_subsystem(
smd_xprtp->edge);
rc = smd_named_open_on_edge(smd_xprtp->ch_name,
smd_xprtp->edge,
&smd_xprtp->channel,
smd_xprtp,
msm_ipc_router_smd_remote_notify);
//modem有数据发给linux时,触发msm_ipc_router_smd_remote_notify
}

注意:此处需要一个IPCRTR平台设备才能触发probe,IPCRTR设备在哪里创建?

IPCRTR平台设备的创建在SDM里

QMI数据收发

发送数据

应用层发数据到达qcci_ipc_router_ops->xport_send,细节在qmi客户端有讲

xport_send的数据到到kernel的msm_ipc_router_sendmsg

  • xport_send发数据时携带了服务地址,数据由kernel路由到指定的服务
  • msm_ipc_router_sendmsg发数据时携带源端口(包含client进程sock信息)

继续调用发送:msm_ipc_router_send_to

1
2
3
4
5
6
...
//构造数据包
pkt = create_pkt(data);
...
//发送报文
ret = msm_ipc_router_write_pkt(src, rport_ptr, pkt, timeout);

填充包头&发送:msm_ipc_router_write_pkt

1
2
3
4
5
6
7
8
9
10
11
12
hdr = &(pkt->hdr);
hdr->version = IPC_ROUTER_V1;
hdr->type = IPC_ROUTER_CTRL_CMD_DATA;
hdr->src_node_id = src->this_port.node_id;
hdr->src_port_id = src->this_port.port_id;
hdr->size = pkt->length;
hdr->control_flag = 0;
hdr->dst_node_id = rport_ptr->node_id;
hdr->dst_port_id = rport_ptr->port_id;

/*使用*/
ret = xprt_info->xprt->write(pkt, pkt->length, xprt_info->xprt);

其中 xprt_info->xprt在msm_ipc_router_smd_config_init中创建,对应的write函数为

msm_ipc_router_smd_remote_write —> smd_write_segment

1
2
3
4
5
int smd_write_segment(smd_channel_t *ch, const void *data, int len)
{
bytes_written = smd_stream_write(ch, data, len, true);
//其中ch在smd_alloc_channel中创建,对应的通道为IPCRTR
}

接收数据

就用层打开IPC_MSM类型的sock,从kernel的msm_ipc_router_recvmsg接收数据

1
2
3
4
5
6
7
8
9
10
static int msm_ipc_router_recvmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *m, size_t buf_len, int flags)
{
//等待数据
ret = msm_ipc_router_rx_data_wait(port_ptr, timeout);
//读取数据
ret = msm_ipc_router_read(port_ptr, &pkt, buf_len);
//解析数据
ret = msm_ipc_router_extract_msg(m, pkt);
}

读取数据,从列队中取出数据

1
2
3
4
5
6
int msm_ipc_router_read(struct msm_ipc_port *port_ptr,
struct rr_packet **read_pkt,
size_t buf_len)
{
pkt = list_first_entry(&port_ptr->port_rx_q, struct rr_packet, list);
}

重点是数据怎么放入队列中

1
2
3
4
5
6
7
8
9
static void do_read_data(struct work_struct *work)
{
post_pkt_to_port(port_ptr, pkt, 0);
}
static int post_pkt_to_port(struct msm_ipc_port *port_ptr,
struct rr_packet *pkt, int clone)
{
list_add_tail(&temp_pkt->list, &port_ptr->port_rx_q);
}

其中do_read_data的调用在Modem发数据到Linux有详细说明

加密认证

客户端发送数据之前需要配置加密认证方式,否则不能发数据

1
2
3
4
5
6
static int msm_ipc_router_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *m, size_t total_len)
{
if (port_ptr->type == CLIENT_PORT)
wait_for_irsc_completion(); //会一直等待认证配置完成
}

认证配置由应用层irsc_util进程负责

irsc_util

ipc router转发数据之前需要配置加密,irsc_util进程负责设置配置

系统启动时执行:/usr/bin/irsc_util /etc/sec_config

默认情况不存在/etc/sec_config文件,则使用默认配置

irsc_util代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fd = socket(AF_MSM_IPC, SOCK_DGRAM, 0);
if (!irsc_d->sec_info || !irsc_d->sec_info->num_entries) {
arg = calloc(1, (sizeof(*arg) + 1 * sizeof(uint32_t)));
if (!arg) {
IRSC_ERR("Calloc failure, Config feeding error\n");
close(fd);
return IRSC_NO_MEM;
}
//没有配置,发送IPC_ROUTER_IOCTL_CONFIG_SEC_RULES命令,使用启用默认配置
if (ioctl(fd, IPC_ROUTER_IOCTL_CONFIG_SEC_RULES命令,, arg) < 0) {
IRSC_DEBUG("Absent/Invalid config,Default rules apply\n");
}
free(arg);
close(fd);
return IRSC_INVALID_FILE;
}

进入到内核代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
case IPC_ROUTER_IOCTL_CONFIG_SEC_RULES:
ret = msm_ipc_config_sec_rules((void *)arg);
if (ret != -EPERM)
port_ptr->type = IRSC_PORT;
break;

//socket关闭时
if (port_ptr->type == IRSC_PORT) {
down_write(&local_ports_lock_lhc2);
list_del(&port_ptr->list);
up_write(&local_ports_lock_lhc2);
signal_irsc_completion(); //通知irsc完成
}

Modem发数据到Linux

modem发数据给linux总体思路

  • 数据存入共享内存
  • 通知AP侧(linux侧)的CPU
  • AP侧CPU收到中断
  • 中断中处理数据:handle_smd_irq

具体数据处理函数在smd_alloc_channel注册

modem侧打开端口

modem发数据之前发送打开端口命令,触发msm_ipc_router_smd_remote_notify

事件是SMD_EVENT_OPEN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case SMD_EVENT_OPEN:
xprt_work = kmalloc(sizeof(struct msm_ipc_router_smd_xprt_work),
GFP_ATOMIC);
if (!xprt_work) {
IPC_RTR_ERR(
"%s: Couldn't notify %d event to IPC Router\n",
__func__, event);
return;
}
//后续数据收发使用xprt_work->xprt,即使用smd提供的xprt
xprt_work->xprt = &smd_xprtp->xprt;
INIT_WORK(&xprt_work->work, smd_xprt_open_event);
queue_work(smd_xprtp->smd_xprt_wq, &xprt_work->work);
break;

继续执行打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void smd_xprt_open_event(struct work_struct *work)
{
struct msm_ipc_router_smd_xprt_work *xprt_work =
container_of(work, struct msm_ipc_router_smd_xprt_work, work);
struct msm_ipc_router_smd_xprt *smd_xprtp =
container_of(xprt_work->xprt,
struct msm_ipc_router_smd_xprt, xprt);
unsigned long flags;

spin_lock_irqsave(&smd_xprtp->ss_reset_lock, flags);
smd_xprtp->ss_reset = 0;
spin_unlock_irqrestore(&smd_xprtp->ss_reset_lock, flags);
msm_ipc_router_xprt_notify(xprt_work->xprt,//后续数据收发使用xprt_work->xprt
IPC_ROUTER_XPRT_EVENT_OPEN, NULL); //打开命令
D("%s: Notified IPC Router of %s OPEN\n",
__func__, xprt_work->xprt->name);
kfree(xprt_work);
}

继续打开

1
2
3
4
5
6
7
8
9
10
11
12
13
case IPC_ROUTER_XPRT_EVENT_OPEN:
xprt_work = kmalloc(sizeof(struct msm_ipc_router_xprt_work),
GFP_ATOMIC);
if (xprt_work) {
xprt_work->xprt = xprt;//后续数据收发使用这个
INIT_WORK(&xprt_work->work, xprt_open_worker);
queue_work(msm_ipc_router_workqueue, &xprt_work->work);
} else {
IPC_RTR_ERR(
"%s: malloc failure - Couldn't notify OPEN event",
__func__);
}
break;

添加一个端口

1
2
3
4
5
6
7
8
static void xprt_open_worker(struct work_struct *work)
{
struct msm_ipc_router_xprt_work *xprt_work =
container_of(work, struct msm_ipc_router_xprt_work, work);

msm_ipc_router_add_xprt(xprt_work->xprt);//后续数据收发使用xprt_work->xprt
kfree(xprt_work);
}

创建读取任务

1
INIT_WORK(&xprt_info->read_data, do_read_data);

接收modem数据

modem有数据发给linux时,触发msm_ipc_router_smd_remote_notify

事件是SMD_EVENT_DATA

1
2
3
4
5
6
7
case SMD_EVENT_DATA:
if (smd_read_avail(smd_xprtp->channel))
queue_delayed_work(smd_xprtp->smd_xprt_wq,
&smd_xprtp->read_work, 0);
if (smd_write_segment_avail(smd_xprtp->channel))
wake_up(&smd_xprtp->write_avail_wait_q);
break;

其中md_xprtp->read_work在msm_ipc_router_smd_config_init里初始化

1
INIT_DELAYED_WORK(&smd_xprtp->read_work, smd_xprt_read_data);

smd_xprt_read_data

  • 从modem侧读取数据包,放入队列

    1
    2
    3
    4
    skb_queue_tail(smd_xprtp->in_pkt->pkt_fragment_q, ipc_rtr_pkt);
    msm_ipc_router_xprt_notify(&smd_xprtp->xprt,
    IPC_ROUTER_XPRT_EVENT_DATA,
    (void *)smd_xprtp->in_pkt);

    继续放入队列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void msm_ipc_router_xprt_notify(struct msm_ipc_router_xprt *xprt,
    unsigned event,
    void *data)
    {
    pkt = clone_pkt((struct rr_packet *)data);
    if (!pkt)
    return;

    mutex_lock(&xprt_info->rx_lock_lhb2);
    list_add_tail(&pkt->list, &xprt_info->pkt_list);
    __pm_stay_awake(&xprt_info->ws);
    mutex_unlock(&xprt_info->rx_lock_lhb2);
    queue_work(xprt_info->workqueue, &xprt_info->read_data);
    }

唤醒读取任务,执行do_read_data

处理modem的数据

do_read_data

modem发过来的数据包格式

1
2
3
4
5
6
7
8
9
10
struct rr_header_v1 {
uint32_t version;
uint32_t type;
uint32_t src_node_id;
uint32_t src_port_id;
uint32_t control_flag;
uint32_t size;
uint32_t dst_node_id;
uint32_t dst_port_id;
};

按照不同类型创建执行不同操作,其中控制消息

1
2
3
4
if (hdr->type != IPC_ROUTER_CTRL_CMD_DATA) {
process_control_msg(xprt_info, pkt);
goto read_next_pkt1;
}

其中包含创建服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch (msg->cmd) {
case IPC_ROUTER_CTRL_CMD_HELLO:
rc = process_hello_msg(xprt_info, msg, hdr);
break;
case IPC_ROUTER_CTRL_CMD_RESUME_TX:
rc = process_resume_tx_msg(msg, pkt);
break;
case IPC_ROUTER_CTRL_CMD_NEW_SERVER: //创建服务
rc = process_new_server_msg(xprt_info, msg, pkt);
break;
case IPC_ROUTER_CTRL_CMD_REMOVE_SERVER:
rc = process_rmv_server_msg(xprt_info, msg, pkt);
break;
case IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT:
rc = process_rmv_client_msg(xprt_info, msg, pkt);
break;
default:
rc = -ENOSYS;
}

SMD

共享内存设备

partitions

smem_areas

初始化

下表为支持了smd(共享内存设备)

1
2
3
4
5
6
7
8
9
10
11
12
13
enum {
SMEM_APPS,
SMEM_MODEM, //modem设备
SMEM_Q6,
SMEM_DSPS,
SMEM_WCNSS,
SMEM_MODEM_Q6_FW,
SMEM_RPM,
SMEM_TZ,
SMEM_SPSS,
SMEM_HYP,
NUM_SMEM_SUBSYSTEMS,
};

linux启动时会为其初始化,创建工作队列probe_work

1
2
3
4
5
6
for (i = 0; i < NUM_SMD_SUBSYSTEMS; ++i) {
remote_info[i].remote_pid = i;
remote_info[i].free_space = UINT_MAX;
INIT_WORK(&remote_info[i].probe_work, smd_channel_probe_worker);
INIT_LIST_HEAD(&remote_info[i].ch_list);
}

一个远端设备有多个通道,默认支持64个通道

创建SDM设备

modem启动后会向linux发消息,触发smd中断,linux侧执行smd_modem_irq_handler

smd_modem_irq_handler —> handle_smd_irq —> do_smd_probe

1
2
3
4
5
6
7
8
9
10
static void do_smd_probe(unsigned remote_pid)
{
unsigned free_space;

free_space = smem_get_free_space(remote_pid);
if (free_space != remote_info[remote_pid].free_space) {
remote_info[remote_pid].free_space = free_space;
schedule_work(&remote_info[remote_pid].probe_work);
}
}

远端设备(比如modem)通过共享内存区域传递数据到linux侧,如果有free_space有变化(细节暂不研究)则触发smd_channel_probe_worker

smd_channel_probe_worker —> smd_channel_probe_now —> smd_alloc_channel

  • 从共享内存获取设备信息,得到设备名字(比如IPCRTR)

  • 遍历所有通道 ,对未申请的通道进行申请

  • 申请通道时会注册平台设备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm, int table_id,
    struct remote_proc_info *r_info)
    {
    /* probe_worker guarentees ch->type will be a valid type */
    if (ch->type == SMD_APPS_MODEM) //设备类型是SMD_APPS_MODEM
    ch->notify_other_cpu = notify_modem_smd;
    else if (ch->type == SMD_APPS_QDSP)
    ch->notify_other_cpu = notify_dsp_smd;
    else if (ch->type == SMD_APPS_DSPS)
    ch->notify_other_cpu = notify_dsps_smd;
    else if (ch->type == SMD_APPS_WCNSS)
    ch->notify_other_cpu = notify_wcnss_smd;
    else if (ch->type == SMD_APPS_Q6FW)
    ch->notify_other_cpu = notify_modemfw_smd;
    else if (ch->type == SMD_APPS_RPM)
    ch->notify_other_cpu = notify_rpm_smd;

    if (smd_is_packet(alloc_elm)) {
    //是包类型
    ch->read = smd_packet_read;
    ch->write = smd_packet_write;
    ch->read_avail = smd_packet_read_avail;
    ch->write_avail = smd_packet_write_avail;
    ch->update_state = update_packet_state;
    ch->read_from_cb = smd_packet_read_from_cb;
    ch->is_pkt_ch = 1;
    } else {
    ch->read = smd_stream_read;
    ch->write = smd_stream_write;
    ch->read_avail = smd_stream_read_avail;
    ch->write_avail = smd_stream_write_avail;
    ch->update_state = update_stream_state;
    ch->read_from_cb = smd_stream_read;
    }

    if (is_word_access_ch(ch->type)) {
    ch->read_from_fifo = smd_memcpy32_from_fifo;
    ch->write_to_fifo = smd_memcpy32_to_fifo;
    } else {
    ch->read_from_fifo = smd_memcpy_from_fifo;
    ch->write_to_fifo = smd_memcpy_to_fifo;
    }

    smd_memcpy_from_fifo(ch->name, alloc_elm->name, SMD_MAX_CH_NAME_LEN);
    ch->name[SMD_MAX_CH_NAME_LEN-1] = 0;

    ch->pdev.name = ch->name;
    ch->pdev.id = ch->type;

    SMD_INFO("smd_alloc_channel() '%s' cid=%d\n",
    ch->name, ch->n);

    mutex_lock(&smd_creation_mutex);
    list_add(&ch->ch_list, &smd_ch_closed_list);
    mutex_unlock(&smd_creation_mutex);

    platform_device_register(&ch->pdev);

启动时会为modem创建多个通道,比如

IPCRTR:用于QMI数据收发

DATA40_CNTL:qti进程使用这个通道,对应的设备名字smdcntl8(在设备树中定义)

smdcntl设备

设备树中包含了smdpkt设备,linux启动时执行msm_smd_pkt_probe —> smd_pkt_devicetree_init

smd_pkt_devicetree_init查询设备树中smdpkt的配置,创建字符设备,操作函数为smd_pkt_fops

打开设备

1
2
3
4
5
6
7
8
9
10
11
12
int smd_pkt_open(struct inode *inode, struct file *file)
{
//为SMD平台设备(比如DATA40_CNTL)添加平台驱动
r = smd_pkt_add_driver(smd_pkt_devp);
if (smd_pkt_devp->ch == 0) {
//打开SMD通道
r = smd_named_open_on_edge(smd_pkt_devp->ch_name,
smd_pkt_devp->edge,
&smd_pkt_devp->ch,
smd_pkt_devp,
ch_notify);//远端有数据发给linux时,调用ch_notify
}

写数据

数据直接写入对应的smd通道里

1
2
3
4
5
6
7
8
9
10
11
ssize_t smd_pkt_write(struct file *file,
const char __user *_buf,
size_t count,
loff_t *ppos)
{

r = smd_write_start(smd_pkt_devp->ch, count);
...
r = smd_write_segment(smd_pkt_devp->ch,
(void *)(buf + bytes_written),
(count - bytes_written));

读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

ssize_t smd_pkt_read(struct file *file,
char __user *_buf,
size_t count,
loff_t *ppos)
{
//等待数据
r = wait_event_interruptible(smd_pkt_devp->ch_read_wait_queue,
!smd_pkt_devp->ch ||
(smd_cur_packet_size(smd_pkt_devp->ch) > 0
&& smd_read_avail(smd_pkt_devp->ch)) ||
smd_pkt_devp->has_reset);
//读取数据
pkt_size = smd_cur_packet_size(smd_pkt_devp->ch);

r = smd_read(smd_pkt_devp->ch,
(buf + bytes_read),
(pkt_size - bytes_read));

打开时通道时注册了回调ch_notify

1
2
3
4
5
static void ch_notify(void *priv, unsigned event)
{
switch (event) {
case SMD_EVENT_DATA: {
check_and_wakeup_reader(smd_pkt_devp);//唤醒读取等待进程